#include <string>
#include <iostream>
#include <sstream>
#include <deque>
#include <tuple>
#include <mutex>
#include <memory>
#include <boost/locale.hpp>
#include <boost/dll/shared_library.hpp>
#include <boost/filesystem.hpp>
#include <boost/exception/all.hpp>
#include <boost/process.hpp>
#ifdef _WIN32
#include <boost/process/windows.hpp>
#endif
//#define BOOST_STACKTRACE_USE_ADDR2LINE
#include <boost/stacktrace.hpp>

#include "gen-cpp/CallService.h"
#include <thrift/protocol/TBinaryProtocol.h>
#include <thrift/server/TSimpleServer.h>
#include <thrift/transport/TServerSocket.h>
#include <thrift/transport/TBufferTransports.h>

using namespace ::apache::thrift;
using namespace ::apache::thrift::protocol;
using namespace ::apache::thrift::transport;
using namespace ::apache::thrift::server;

#include <engine.h>
#include <matrix.h>

# define _snprintf snprintf
#include <stdio.h>
#ifdef _WIN32
#define strcasecmp stricmp
#else
#include <strings.h>
#endif

#include <Python.h>
using namespace std;
using namespace boost;
#define MATLABSIZE 1024

atomic<bool> is_exit=false;
Engine* engine=nullptr;
condition_variable cbegin_value,cend_value,rbegin_value,rend_value;
mutex task_mutex,recv_mutex,return_mutex;
deque<std::tuple<string,string,string>> tasks;
string invoke_cplus(string file_path,string func_name,string value)
{
    printf("##c++:%s\n",file_path.c_str());
    dll::shared_library lib;
    lib.load(file_path);
    if(!lib.has(func_name)) {
        cout << "can not find export symbol";
        lib.unload();
        return "";
    }
    string(*func)(string&) = lib.get<string(string&)>(func_name);
    if(value.empty()) {
        cout << "can not find parameter";
        lib.unload();
        return "";
    }

    string ret;
    ret = func(value);
    lib.unload();
    return ret;
}
string invoke_matlab(string file_path,string value)
{
    printf("##matlab:%s\n",file_path.c_str());
    char textOut[MATLABSIZE] = {};
    boost::filesystem::path filepath(file_path);
    engEvalString(engine,"clear all;clc;");
    engOutputBuffer(engine,textOut,sizeof(textOut));
    engEvalString(engine,("addpath('"+boost::filesystem::absolute(filepath).parent_path().string()+"')").c_str());
    if(value.empty()){
        engEvalString(engine,("run(\""+filepath.string()+"\")").c_str());
    }else{
        engEvalString(engine,(filepath.stem().string()+"(jsondecode('"+value+"'))").c_str());
    }
    stringstream ret;
    ret<<"------------------------------"<<endl<<
        "filename:"<<file_path<<endl<<"parameter:"<<value<<endl<<"output:";
    ret <<endl<< textOut << endl << "ans:";
    engEvalString(engine,"jsonencode(ans)");
    mxArray* result = engGetVariable(engine,"ans");
    if(result!=nullptr){
        char* p = mxArrayToString(result);
        if(p) {
            ret << p <<endl;
            return p;
        }
        else ret<<endl;
    }else{
        ret<<endl;
    }
    mxDestroyArray(result);
    ret<<"------------------------------"<<endl;
    cout<<ret.str()<<flush;
    return ret.str();
}
string invoke_python(string file_path,string value)
{
    printf("##python:%s\n",file_path.c_str());
    string line,ret;
    std::error_code ec;
    process::ipstream is;
#ifdef _WIN32
    process::system("where python", process::std_out>is, 
        ec,process::windows::create_no_window);
#else
    process::child c("whereis python3", process::std_out>is);
    c.wait();
#endif
    std::getline(is, line);
    Py_SetPythonHome(boost::locale::conv::to_utf<wchar_t>(
        boost::filesystem::path(line).parent_path().string(), "gbk").c_str());
    Py_Initialize();
    if(!Py_IsInitialized()){
        cout<<"init python error";
        return "";
    }
    PyRun_SimpleString(("import sys\nsys.path.append(r'"+boost::filesystem::path(file_path).parent_path().string()+"')").c_str());
    PyObject* pyModule = PyImport_ImportModuleNoBlock(boost::filesystem::path(file_path).stem().string().c_str());
    if (pyModule != NULL) {
        PyObject* pyFunc = PyObject_GetAttrString(pyModule, boost::filesystem::path(file_path).stem().string().c_str());
        if (pyFunc != NULL) {
            if (PyCallable_Check(pyFunc) != 0){
                PyObject* result = NULL;
                result = PyObject_CallFunction(pyFunc,value.c_str());
                if (result != NULL) Py_DECREF(result);
                ret = (char*)PyUnicode_DATA(PyObject_Str(result));
            }
            Py_DECREF(pyFunc);
        }
        Py_DECREF(pyModule);
    }
    
    PyErr_Print();   //打印错误
    PyErr_Clear();   //清理错误
    Py_Finalize();//退出前清理，否则异常
    return ret;
}
class MultiServer : public TServerFramework {
public:
    MultiServer(
      const std::shared_ptr<apache::thrift::TProcessorFactory>& processorFactory,
      const std::shared_ptr<apache::thrift::transport::TServerTransport>& serverTransport,
      const std::shared_ptr<apache::thrift::transport::TTransportFactory>& transportFactory,
      const std::shared_ptr<apache::thrift::protocol::TProtocolFactory>& protocolFactory):
        TServerFramework(processorFactory, serverTransport, transportFactory, protocolFactory){
      }

    MultiServer(
      const std::shared_ptr<apache::thrift::TProcessor>& processor,
      const std::shared_ptr<apache::thrift::transport::TServerTransport>& serverTransport,
      const std::shared_ptr<apache::thrift::transport::TTransportFactory>& transportFactory,
      const std::shared_ptr<apache::thrift::protocol::TProtocolFactory>& protocolFactory):
        TServerFramework(processor, serverTransport, transportFactory, protocolFactory){
      }
    MultiServer(
      const std::shared_ptr<apache::thrift::TProcessorFactory>& processorFactory,
      const std::shared_ptr<apache::thrift::transport::TServerTransport>& serverTransport,
      const std::shared_ptr<apache::thrift::transport::TTransportFactory>& inputTransportFactory,
      const std::shared_ptr<apache::thrift::transport::TTransportFactory>& outputTransportFactory,
      const std::shared_ptr<apache::thrift::protocol::TProtocolFactory>& inputProtocolFactory,
      const std::shared_ptr<apache::thrift::protocol::TProtocolFactory>& outputProtocolFactory):
        TServerFramework(processorFactory, serverTransport, inputTransportFactory, outputTransportFactory,inputProtocolFactory,outputProtocolFactory){
      }

    MultiServer(
      const std::shared_ptr<apache::thrift::TProcessor>& processor,
      const std::shared_ptr<apache::thrift::transport::TServerTransport>& serverTransport,
      const std::shared_ptr<apache::thrift::transport::TTransportFactory>& inputTransportFactory,
      const std::shared_ptr<apache::thrift::transport::TTransportFactory>& outputTransportFactory,
      const std::shared_ptr<apache::thrift::protocol::TProtocolFactory>& inputProtocolFactory,
      const std::shared_ptr<apache::thrift::protocol::TProtocolFactory>& outputProtocolFactory):
        TServerFramework(processor, serverTransport, inputTransportFactory, outputTransportFactory, inputProtocolFactory, outputProtocolFactory){
      }

    ~MultiServer() override{};
    
protected:
  void onClientConnected(const std::shared_ptr<TConnectedClient>& pClient) override {
    cout<<"connect servers count,"<<getConcurrentClientCount()<<endl;
    pClient->run();
  }
  void onClientDisconnected(TConnectedClient* pClient) override {
  }
};
class CallServiceHandler : virtual public CallServiceIf {
public:
    CallServiceHandler() {
    }
    void Process(std::string& _return, const std::string& file, const std::string& value) {
        // Your implementation goes here
        {
            lock_guard<mutex> lock(task_mutex);
            ((string&)file) = boost::locale::conv::from_utf(file,"gbk");
            ((string&)value) = boost::locale::conv::from_utf(value,"gbk");
            tasks.emplace_back(make_tuple(file,value,string()));
        }
        
        unique_lock<mutex> r_lock(return_mutex);
        cbegin_value.notify_all();
        rbegin_value.wait(r_lock);
        {
            lock_guard<mutex> lock(task_mutex);
            _return = get<2>(tasks.front());
            _return = boost::locale::conv::to_utf<char>(_return,"gbk");
            tasks.erase(tasks.begin());
        }
        cend_value.notify_all();
        rend_value.wait(r_lock);
    }
};

int main(int argc, char* argv[])
{
    int port = argc>1?atoi(argv[1]):0;
    if(port==0){
        cout<<"port parameter error."<<endl;
        return 0;
    }
    std::thread([&]{
        while(true)
        {
            if(argc<=2) return;
            int n_pid = std::stoul(argv[2]);
            try{
                boost::process::child child(static_cast<boost::process::detail::api::pid_t>(n_pid));
                if(child.running()){
                    this_thread::sleep_for(chrono::microseconds(10));
                }else{
                    cbegin_value.notify_all();
                    cend_value.notify_all();
                    is_exit=true;
                }
            }catch(...){
                cbegin_value.notify_all();
                cend_value.notify_all();
                is_exit=true;
            }
        }
    }).detach();
    engine = engOpen(0);
    engSetVisible(engine,false);
    if(!engine) cout<<"matlab engine start error"<<endl;
    std::thread([&]{
        try {
            ::std::shared_ptr<CallServiceHandler> handler(new CallServiceHandler());
            ::std::shared_ptr<TProcessor> processor(new CallServiceProcessor(handler));
            ::std::shared_ptr<TServerTransport> serverTransport(new TServerSocket(port));
            ::std::shared_ptr<TTransportFactory> transportFactory(new TBufferedTransportFactory());
            ::std::shared_ptr<TProtocolFactory> protocolFactory(new TBinaryProtocolFactory());

            MultiServer server(processor, serverTransport, transportFactory, protocolFactory);
            server.serve();
        }catch (boost::exception &e) {
            cout << boost::stacktrace::stacktrace() << endl;
            cout << boost::diagnostic_information(e) << endl;
        }
    }).detach();
    while(!is_exit){
        unique_lock<mutex> c_lock(recv_mutex);
        try {
            rend_value.notify_all();
            cbegin_value.wait(c_lock);
            string file_path,value;
            {
                lock_guard<mutex> lock(task_mutex);
                if(tasks.size()==0) continue;
                file_path = get<0>(tasks.front());
                value = get<1>(tasks.front());
            }
            string ret;
            boost::filesystem::path filepath(file_path);
            string extension = filepath.extension().string();

            if(extension ==".so")
                ret = invoke_cplus(file_path,filepath.stem().string().substr(0,3)=="lib"?
                    filepath.stem().string().substr(3):filepath.stem().string(),value);
            else if(extension ==".dll")
                ret = invoke_cplus(file_path,filepath.stem().string(),value);
            else if(extension == ".m")
                ret = invoke_matlab(file_path,value);
            else if(extension == ".py")
                ret = invoke_python(file_path,value);
            else
                printf("unknow file type:%s\n",file_path.c_str());

            {
                lock_guard<mutex> lock(task_mutex);
                get<2>(tasks.front()) = ret;
            }
            rbegin_value.notify_all();
            cend_value.wait(c_lock);
        }
        catch (boost::exception &e) {
            rbegin_value.notify_all();
            cend_value.wait(c_lock);
            cout << boost::stacktrace::stacktrace() << endl;
            cout << boost::diagnostic_information(e) << endl;
        }
    }
    if(engine) engClose(engine);
    return 0;
}